#ifndef REST_RPC_ROUTER_H_
#define REST_RPC_ROUTER_H_

#include "codec.h"
#include "meta_util.hpp"
#include "use_asio.hpp"
#include <functional>

/**
 * @brief
 * The router class in this code defines a class for routing incoming requests to the appropriate handlers.
 * The class provides a set of register_handler functions that allow users to register functions to handle incoming requests.
 * The functions take two template arguments: ExecMode and a Function or Self type.
 * The ExecMode argument can be either ExecMode::sync or ExecMode::async and determines whether the function should be executed synchronously or asynchronously.
 * The Function type is a function or member function that takes a std::weak_ptr<connection> and some number of additional arguments,
 * and returns either void or a result that can be serialized to a std::string.
 * The route function takes as input a buffer of data, the size of the buffer, and a std::weak_ptr<T> to a connection, which is used to send the response.
 * Inside the function, it checks whether the connection is valid, then it uses the msgpack_codec to deserialize the request.
 * It extracts the function name from the request and looks it up in the map of invokers.
 * If the function is found it calls the corresponding function, passing in the connection, data, size, and result.
 * If the execution mode is sync, it sends the response back to the client, otherwise the response will be sent back by the async function it self.
 * If there is an exception, it sends an error response back to the client with the error message.
 * The remove_handler function removes a registered function from the map of invokers based on its name，
 * so it will no longer be called for incoming requests.
 * The rest of the code contains several private functions that are used to implement the functionality of the router class
 * such as call_helper and call which are used to call the registered functions with the appropriate arguments and return the result in the correct format.
 */

namespace rest_rpc
{
  // ExecMode could be sync or async.
  enum class ExecMode
  {
    sync,
    async
  };
  const constexpr ExecMode Async = ExecMode::async;

  namespace rpc_service
  {
    class connection;

    class router : asio::noncopyable
    {
    public:
      template <ExecMode model, typename Function>
      void register_handler(std::string const &name, Function f)
      {
        return register_nonmember_func<model>(name, std::move(f));
      }

      template <ExecMode model, typename Function, typename Self>
      void register_handler(std::string const &name, const Function &f, Self *self)
      {
        return register_member_func<model>(name, f, self);
      }

      void remove_handler(std::string const &name)
      {
        this->map_invokers_.erase(name);
      }

      template <typename T>
      void route(const char *data, std::size_t size, std::weak_ptr<T> conn)
      {
        auto conn_sp = conn.lock();
        if (!conn_sp)
        {
          return;
        }

        auto req_id = conn_sp->request_id();
        std::string result;
        try
        {
          msgpack_codec codec;
          auto p = codec.unpack<std::tuple<std::string>>(data, size);
          auto &func_name = std::get<0>(p);
          auto it = map_invokers_.find(func_name);
          if (it == map_invokers_.end())
          {
            result = codec.pack_args_str(result_code::FAIL, "unknown function: " + func_name);
            conn_sp->response(req_id, std::move(result));
            return;
          }

          ExecMode model;
          it->second(conn, data, size, result, model);
          if (model == ExecMode::sync)
          {
            if (result.size() >= MAX_BUF_LEN)
            {
              result = codec.pack_args_str(
                  result_code::FAIL,
                  "the response result is out of range: more than 10M " +
                      func_name);
            }
            conn_sp->response(req_id, std::move(result));
          }
        }
        catch (const std::exception &ex)
        {
          msgpack_codec codec;
          result = codec.pack_args_str(result_code::FAIL, ex.what());
          conn_sp->response(req_id, std::move(result));
        }
      }

      router() = default;

    private:
      router(const router &) = delete;
      router(router &&) = delete;

      template <typename F, size_t... I, typename Arg, typename... Args>
      static typename std::result_of<F(std::weak_ptr<connection>, Args...)>::type
      call_helper(const F &f, const std::index_sequence<I...> &, std::tuple<Arg, Args...> tup, std::weak_ptr<connection> ptr)
      {
        return f(ptr, std::move(std::get<I + 1>(tup))...);
      }

      template <typename F, typename Arg, typename... Args>
      static typename std::enable_if<std::is_void<typename std::result_of<F(std::weak_ptr<connection>, Args...)>::type>::value>::type
      call(const F &f, std::weak_ptr<connection> ptr, std::string &result, std::tuple<Arg, Args...> tp)
      {
        call_helper(f, std::make_index_sequence<sizeof...(Args)>{}, std::move(tp), ptr);
        result = msgpack_codec::pack_args_str(result_code::OK);
      }

      template <typename F, typename Arg, typename... Args>
      static typename std::enable_if<!std::is_void<typename std::result_of<F(std::weak_ptr<connection>, Args...)>::type>::value>::type
      call(const F &f, std::weak_ptr<connection> ptr, std::string &result, std::tuple<Arg, Args...> tp)
      {
        auto r = call_helper(f, std::make_index_sequence<sizeof...(Args)>{}, std::move(tp), ptr);
        msgpack_codec codec;
        result = msgpack_codec::pack_args_str(result_code::OK, r);
      }

      template <typename F, typename Self, size_t... Indexes, typename Arg, typename... Args>
      static typename std::result_of<F(Self, std::weak_ptr<connection>, Args...)>::type
      call_member_helper(const F &f, Self *self,
                         const std::index_sequence<Indexes...> &,
                         std::tuple<Arg, Args...> tup,
                         std::weak_ptr<connection> ptr = std::shared_ptr<connection>{nullptr})
      {
        return (*self.*f)(ptr, std::move(std::get<Indexes + 1>(tup))...);
      }

      template <typename F, typename Self, typename Arg, typename... Args>
      static typename std::enable_if<std::is_void<typename std::result_of<F(Self, std::weak_ptr<connection>, Args...)>::type>::value>::type
      call_member(const F &f, Self *self, std::weak_ptr<connection> ptr, std::string &result, std::tuple<Arg, Args...> tp)
      {
        call_member_helper(f,
                           self,
                           typename std::make_index_sequence<sizeof...(Args)>{},
                           std::move(tp), ptr);
        result = msgpack_codec::pack_args_str(result_code::OK);
      }

      template <typename F, typename Self, typename Arg, typename... Args>
      static typename std::enable_if<!std::is_void<typename std::result_of<F(Self, std::weak_ptr<connection>, Args...)>::type>::value>::type
      call_member(const F &f, Self *self, std::weak_ptr<connection> ptr, std::string &result, std::tuple<Arg, Args...> tp)
      {
        auto r = call_member_helper(f, self, typename std::make_index_sequence<sizeof...(Args)>{}, std::move(tp), ptr);
        result = msgpack_codec::pack_args_str(result_code::OK, r);
      }

      /**
       * @brief struct invoker.
       * This is a template struct invoker, which has a single template parameter Function and a default template parameter ExecMode mode = ExecMode::sync.
       * The invoker struct has a static member function apply that takes a const reference to a Function, a std::weak_ptr<connection>, a pointer to a character array (const char*),
       * a size_t size, a reference to a std::string and a reference to an ExecMode as arguments.
       * The apply function first sets the exe_model to ExecMode::sync, then creates an instance of the msgpack_codec struct.
       * It then uses the unpack function of the msgpack_codec class to unpack the data received in the character array and convert it into an args_tuple, which is a tuple of arguments that the Function takes.
       * The args_tuple type is obtained using the function_traits struct's args_tuple_2nd member type.
       * The apply function then calls the call function passing the Function, the std::weak_ptr<connection>, the std::string result, and the args_tuple as arguments.
       * If an exception of type std::invalid_argument or std::exception is thrown, the apply function catches it,
       * and uses the pack_args_str function of the msgpack_codec class to pack the exception message and the result code result_code::FAIL into the result string.
       * Finally, the apply function sets the exe_model to the provided model
       *
       * @tparam Function
       * @tparam mode
       */
      template <typename Function, ExecMode mode = ExecMode::sync>
      struct invoker
      {
        template <ExecMode model>
        static inline void apply(const Function &func,
                                 std::weak_ptr<connection> conn,
                                 const char *data,
                                 size_t size, std::string &result,
                                 ExecMode &exe_model)
        {
          using args_tuple = typename function_traits<Function>::args_tuple_2nd;
          exe_model = ExecMode::sync;
          msgpack_codec codec;
          try
          {
            auto tp = codec.unpack<args_tuple>(data, size);
            call(func, conn, result, std::move(tp));
            exe_model = model;
          }
          catch (std::invalid_argument &e)
          {
            result = codec.pack_args_str(result_code::FAIL, e.what());
          }
          catch (const std::exception &e)
          {
            result = codec.pack_args_str(result_code::FAIL, e.what());
          }
        }

        /**
         * @brief
         *
         * @tparam model
         * @tparam Self
         * @param func
         * @param self
         * @param conn std::weak_ptr is a non-owning smart pointer. It holds a weak reference to an object managed by another std::shared_ptr.
         *             It is used to break circular references between std::shared_ptr objects, which can prevent objects from being properly destroyed.
         *             A std::weak_ptr can be converted to a std::shared_ptr, but only if the object it references is still alive.
         *
         *
         *             std::unique_ptr is an owning smart pointer. It holds a unique ownership of an object, and it is used to manage the lifetime of dynamically allocated objects.
         *             Once a std::unique_ptr is created, it is the only way to access the object it owns.
         *             It cannot be copied, only moved, and once it goes out of scope, the object it owns will be destroyed. It can be used to replace raw pointers.
         *
         *              std::shared_ptr is a shared-ownership smart pointer. It holds a shared ownership of an object, and it is used to manage the lifetime of dynamically allocated objects.
         *             A std::shared_ptr can be copied multiple times, and the object it manages will only be destroyed when all std::shared_ptr objects that own it are destroyed.
         * @param data
         * @param size
         * @param result
         * @param exe_model
         */
        template <ExecMode model, typename Self>
        static inline void apply_member(const Function &func, Self *self,
                                        std::weak_ptr<connection> conn,
                                        const char *data, size_t size,
                                        std::string &result, ExecMode &exe_model)
        {
          using args_tuple = typename function_traits<Function>::args_tuple_2nd;
          exe_model = ExecMode::sync;
          msgpack_codec codec;
          try
          {
            auto tp = codec.unpack<args_tuple>(data, size);
            call_member(func, self, conn, result, std::move(tp));
            exe_model = model;
          }
          catch (std::invalid_argument &e)
          {
            result = codec.pack_args_str(result_code::FAIL, e.what());
          }
          catch (const std::exception &e)
          {
            result = codec.pack_args_str(result_code::FAIL, e.what());
          }
        }
      };

      /**
       * @brief
       *
       * @tparam model
       * @tparam Function
       * @param name
       * @param f
       *
       * The register_nonmember_func function takes a string (name) and a function (f) as arguments.
       * It uses std::bind to create a new function object that will invoke the apply function of the invoker class template,
       * passing in the function f, as well as five placeholders (_1, _2, _3, _4, and _5) that will be used to pass arguments to the function when it is called.
       * This new function object is then added to the map_invokers_ member variable of the class.
       *
       * The register_member_func function is similar to register_nonmember_func,
       * but it is used to register member functions of a class. It takes a string (name), a function (f), and a pointer to an instance of the class (self) as arguments.
       * Like the register_nonmember_func function, it uses std::bind to create a new function object that will invoke the apply_member function of the invoker class template,
       * passing in the function f, the pointer to the class instance (self) and the five placeholders (_1, _2, _3, _4, and _5) that will be used to pass arguments to the function when it is called.
       * This new function object is also added to the map_invokers_ member variable of the class.
       */
      template <ExecMode model, typename Function>
      void register_nonmember_func(std::string const &name, Function f)
      {
        this->map_invokers_[name] = {std::bind(
            &invoker<Function>::template apply<model>,
            std::move(f),
            std::placeholders::_1,
            std::placeholders::_2,
            std::placeholders::_3,
            std::placeholders::_4,
            std::placeholders::_5)};
      }

      template <ExecMode model, typename Function, typename Self>
      void register_member_func(const std::string &name, const Function &f, Self *self)
      {
        this->map_invokers_[name] = {std::bind(
            &invoker<Function>::template apply_member<model, Self>, f, self,
            std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
            std::placeholders::_4, std::placeholders::_5)};
      }

      std::unordered_map<std::string, std::function<void(std::weak_ptr<connection>, const char *, size_t, std::string &, ExecMode &model)>> map_invokers_;
    };
  } // namespace rpc_service
} // namespace rest_rpc

#endif // REST_RPC_ROUTER_H_
